package linq;

import haxe.macro.Compiler;
import haxe.macro.Context;
import haxe.macro.Expr;

@:autoBuild (linq.SLambdaMacro.build ())
interface SLambda {}

class SLambdaMacro {
	@:macro public static function build ():Array<Field> {
		var local = Context.getBuildFields ();

		for (field in local) {
			switch (field.kind) {
			case FVar (t,e): d (e);
			case FFun (f): d (f.expr);
			case FProp (get, set, t, e): d (e);
			}
		}

		return local;
	}

	@:macro public static function toLambda (e:Expr):Expr {
		d (e);	
		trace (e);
		return e;
	}

	#if macro
	static function d (e:Expr) {
		if (e == null) return;
		//e = l (e);
		switch (e.expr) {
		case EArray (e1,e2): d (e1); d (e2);
		case EBinop (op,e1,e2): if (scan (e)) d (e); else {d (e1); d (e2);}
		case EField (e,field): d (e);
		case EType (e,field): d (e);
		case EParenthesis (e): d (e);
		case EObjectDecl (fields): for (f in fields) d (f.expr);
		case EArrayDecl (values): for (v in values) d (v);
		case ECall (e, params): d (e); for (p in params) d (p);
		case ENew (t, params): for (p in params) d (p);
		case EUnop (op, postFix, e): d (e);
		case EVars (vars): for (v in vars) d (v.expr);
		case EFunction (name, f): d (f.expr);
		case EBlock (exprs): for (e in exprs) d (e);
		case EFor (it, expr): d (it); d (expr);
		case EIn (e1, e2): d (e1); d (e2);
		case EIf (econd, eif, eelse): d (econd); d (eif); d (eelse);
		case EWhile (econd, e, normalWhile): d (econd); d (e);
		case ESwitch (e, cases, edef): d (e);
			for (c in cases) {
				for (v in c.values) d (v);
				d (c.expr);
			}
			d (edef);
		case ETry (e, catches): d (e); for (c in catches) d (c.expr);
		case EReturn (e), EUntyped (e), EThrow (e): d (e);
		case ECast (e, t): d (e);
		case EDisplay (e, isCall): d (e);
		case EDisplayNew (t):
		case ETernary (econd, eif, eelse): d (econd); d (eif); d (eelse);
		case ECheckType (e, t): d (e);
		default:
		}
	}

	static function genLambda (e1:Expr, e2:Expr, ot:Int):ExprDef {
		var args = [];
		switch (e1.expr) {
		case EConst(c):
			switch (c) {
			case CIdent (s), CType (s):
				//trace ("Found Possible Lambda: " + s);
				if (s.charAt (0) == "$") {
					//This is a 1slambda
					if (s.length > 1)
						args.push ({
							name:s.substr (1),
							opt:false,
							type:null,
							value:null
						});
				} else return null;
			default: return null;
			}
		case EArrayDecl (values):
			//trace ("Found Lambda: " + values);
			for (v in values) switch (v.expr) {
			case EConst (c): switch (c) {
				case CIdent (s), CType (s):
					args.push ({
						name:s,
						opt:false,
						type:null,
						value:null
					});
				default:
				}
			default:
			}
		default: return null;
		}
		return EFunction (null, {
			args:args,
			ret:null,
			expr:switch (ot){
				case 1:
					{expr:EReturn (e2), pos:Context.currentPos ()};
				case 2:
					e2;},
			params:[]
		});
	}

	static function scan (base:Expr):Bool {
		var e = base;
		var ct:Int = 0;
		while (true) {
			switch (e.expr) {
			case EBinop (op, e1, e2):
				var ot = switch (op) { case OpGt: 1; case OpShr: 2; default: 0;}
				if (ot > 0) {
					//is Lambda candidate
					var h = base;
					if (ct == 0) h = e2;
					var l = genLambda (e1, {expr:h.expr, pos:h.pos}, ot);
					if (l != null) {
						//Is lambda
						if (ct == 0) {
							base.expr = l;
							return true;
						} else {
							base.expr = l;
							e.expr = e2.expr;
							return true;
						}						
					}
				}
				e = e1;
			default: break;
			}
			ct++;
		}
		return false;
	}
	#end
}